home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / FCGISERVER.PY < prev    next >
Encoding:
Python Source  |  2000-03-29  |  28.3 KB  |  836 lines

  1. ##############################################################################
  2. #
  3. # Zope Public License (ZPL) Version 1.0
  4. # -------------------------------------
  5. #
  6. # Copyright (c) Digital Creations.  All rights reserved.
  7. #
  8. # This license has been certified as Open Source(tm).
  9. #
  10. # Redistribution and use in source and binary forms, with or without
  11. # modification, are permitted provided that the following conditions are
  12. # met:
  13. #
  14. # 1. Redistributions in source code must retain the above copyright
  15. #    notice, this list of conditions, and the following disclaimer.
  16. #
  17. # 2. Redistributions in binary form must reproduce the above copyright
  18. #    notice, this list of conditions, and the following disclaimer in
  19. #    the documentation and/or other materials provided with the
  20. #    distribution.
  21. #
  22. # 3. Digital Creations requests that attribution be given to Zope
  23. #    in any manner possible. Zope includes a "Powered by Zope"
  24. #    button that is installed by default. While it is not a license
  25. #    violation to remove this button, it is requested that the
  26. #    attribution remain. A significant investment has been put
  27. #    into Zope, and this effort will continue if the Zope community
  28. #    continues to grow. This is one way to assure that growth.
  29. #
  30. # 4. All advertising materials and documentation mentioning
  31. #    features derived from or use of this software must display
  32. #    the following acknowledgement:
  33. #
  34. #      "This product includes software developed by Digital Creations
  35. #      for use in the Z Object Publishing Environment
  36. #      (http://www.zope.org/)."
  37. #
  38. #    In the event that the product being advertised includes an
  39. #    intact Zope distribution (with copyright and license included)
  40. #    then this clause is waived.
  41. #
  42. # 5. Names associated with Zope or Digital Creations must not be used to
  43. #    endorse or promote products derived from this software without
  44. #    prior written permission from Digital Creations.
  45. #
  46. # 6. Modified redistributions of any form whatsoever must retain
  47. #    the following acknowledgment:
  48. #
  49. #      "This product includes software developed by Digital Creations
  50. #      for use in the Z Object Publishing Environment
  51. #      (http://www.zope.org/)."
  52. #
  53. #    Intact (re-)distributions of any official Zope release do not
  54. #    require an external acknowledgement.
  55. #
  56. # 7. Modifications are encouraged but must be packaged separately as
  57. #    patches to official Zope releases.  Distributions that do not
  58. #    clearly separate the patches from the original work must be clearly
  59. #    labeled as unofficial distributions.  Modifications which do not
  60. #    carry the name Zope may be packaged in any form, as long as they
  61. #    conform to all of the clauses above.
  62. #
  63. #
  64. # Disclaimer
  65. #
  66. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  67. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  68. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  69. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  70. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  71. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  72. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  73. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  74. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  75. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  76. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  77. #   SUCH DAMAGE.
  78. #
  79. #
  80. # This software consists of contributions made by Digital Creations and
  81. # many individuals on behalf of Digital Creations.  Specific
  82. # attributions are listed in the accompanying credits file.
  83. #
  84. ##############################################################################
  85.  
  86. """
  87. ZServer/Medusa FastCGI server, by Robin Dunn.
  88.  
  89. Accepts connections from a FastCGI enabled webserver, receives request
  90. info using the FastCGi protocol, and then hands the request off to
  91. ZPublisher for processing.  The response is then handed back to the
  92. webserver to send down to the browser.
  93.  
  94. See http://www.fastcgi.com/fcgi-devkit-2.1/doc/fcgi-spec.html for the
  95. protocol specificaition.
  96. """
  97.  
  98. __version__ = "1.0"
  99.  
  100. #----------------------------------------------------------------------
  101.  
  102. from medusa import asynchat, asyncore, logger
  103. from medusa.counter import counter
  104. from medusa.http_server import compute_timezone_for_log
  105.  
  106. from ZServer import CONNECTION_LIMIT
  107.  
  108. from PubCore import handle
  109. from PubCore.ZEvent import Wakeup
  110. from ZPublisher.HTTPResponse import HTTPResponse
  111. from ZPublisher.HTTPRequest import HTTPRequest
  112. from Producers import ShutdownProducer, LoggingProducer, file_part_producer, file_close_producer
  113.  
  114. import DebugLogger
  115.  
  116. from cStringIO import StringIO
  117. from tempfile import TemporaryFile
  118. import socket, string, os, sys, time
  119. from types import StringType
  120. import thread
  121.  
  122. tz_for_log = compute_timezone_for_log()
  123.  
  124. #----------------------------------------------------------------------
  125. # Set various FastCGI constants
  126.  
  127. # Maximum number of requests that can be handled.  Apache mod_fastcgi
  128. # never asks for these values, so we actually will handle as many
  129. # connections/requests as they attempt upto the limits of ZServer.
  130. # These values are suitable defaults for any web server that does ask.
  131. FCGI_MAX_CONNS = 10
  132. FCGI_MAX_REQS  = 50
  133.  
  134. # Supported version of the FastCGI protocol
  135. FCGI_VERSION_1 = 1
  136.  
  137. # Boolean: can this application multiplex connections?
  138. FCGI_MPXS_CONNS=0
  139.  
  140. # Record types
  141. FCGI_BEGIN_REQUEST     = 1
  142. FCGI_ABORT_REQUEST     = 2
  143. FCGI_END_REQUEST       = 3
  144. FCGI_PARAMS            = 4
  145. FCGI_STDIN             = 5
  146. FCGI_STDOUT            = 6
  147. FCGI_STDERR            = 7
  148. FCGI_DATA              = 8
  149. FCGI_GET_VALUES        = 9
  150. FCGI_GET_VALUES_RESULT = 10
  151. FCGI_UNKNOWN_TYPE      = 11
  152. FCGI_MAXTYPE           = FCGI_UNKNOWN_TYPE
  153.  
  154. # Types of management records
  155. FCGI_ManagementTypes = [ FCGI_GET_VALUES ]
  156.  
  157. FCGI_NULL_REQUEST_ID=0
  158.  
  159. # Masks for flags component of FCGI_BEGIN_REQUEST
  160. FCGI_KEEP_CONN = 1
  161.  
  162. # Values for role component of FCGI_BEGIN_REQUEST
  163. FCGI_RESPONDER  = 1
  164. FCGI_AUTHORIZER = 2
  165. FCGI_FILTER     = 3
  166.  
  167. # Values for protocolStatus component of FCGI_END_REQUEST
  168. FCGI_REQUEST_COMPLETE = 0               # Request completed nicely
  169. FCGI_CANT_MPX_CONN    = 1               # This app can't multiplex
  170. FCGI_OVERLOADED       = 2               # New request rejected; too busy
  171. FCGI_UNKNOWN_ROLE     = 3               # Role value not known
  172.  
  173.  
  174. #----------------------------------------------------------------------
  175.  
  176. class FCGIRecord:
  177.     """
  178.     This class represents the various record structures used in the
  179.     FastCGI protocol.  It knows how to read and build itself bits
  180.     at a time as they are read from the FCGIChannel.  There are really
  181.     several different record types but in this case subclassing for
  182.     each type is probably overkill.
  183.  
  184.     See the FastCGI spec for structure and other details for all these
  185.     record types.
  186.     """
  187.     def __init__(self, header=None):
  188.         if header:
  189.             # extract the record header values.
  190.             vals = map(ord, header)
  191.             self.version = vals[0]
  192.             self.recType = vals[1]
  193.             self.reqId = (vals[2] << 8) + vals[3]
  194.             self.contentLength = (vals[4] << 8) + vals[5]
  195.             self.paddingLength = vals[6]
  196.         else:
  197.             self.version = FCGI_VERSION_1
  198.             self.recType = FCGI_UNKNOWN_TYPE
  199.             self.reqId   = FCGI_NULL_REQUEST_ID
  200.         self.content = ""
  201.  
  202.  
  203.     def needContent(self):
  204.         return (self.contentLength and not self.content)
  205.  
  206.     def needPadding(self):
  207.         return self.paddingLength != 0
  208.  
  209.     def needMore(self):
  210.         if self.needContent():
  211.             return self.contentLength
  212.         else:
  213.             return self.paddingLength
  214.  
  215.     def gotPadding(self):
  216.         self.paddingLength = 0
  217.  
  218.  
  219.     def parseContent(self, data):
  220.         c = self.content = data
  221.         if self.recType == FCGI_BEGIN_REQUEST:
  222.             self.role  = (ord(c[0]) << 8) + ord(c[1])
  223.             self.flags = ord(c[2])
  224.  
  225.         elif self.recType == FCGI_UNKNOWN_TYPE:
  226.             self.unknownType = ord(c[0])
  227.  
  228.         elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
  229.             self.values = {}
  230.             pos = 0
  231.             while pos < len(c):
  232.                 name, value, pos = self.readPair(c, pos)
  233.                 self.values[name] = value
  234.  
  235.         elif self.recType == FCGI_END_REQUEST:
  236.             b = map(ord, c[0:4])
  237.             self.appStatus = (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3]
  238.             self.protocolStatus = ord(c[4])
  239.  
  240.  
  241.  
  242.     def readPair(self, st, pos):
  243.         """
  244.         Read the next name-value pair from st at pos.
  245.         """
  246.         nameLen = ord(st[pos])
  247.         pos = pos + 1
  248.         if nameLen & 0x80:  # is the high bit set? if so, size is 4 bytes, not 1.
  249.             b = map(ord, st[pos:pos+3])
  250.             pos = pos + 3
  251.             nameLen = ((nameLen & 0x7F) << 24) + (b[0] << 16) + (b[1] << 8) + b[2]
  252.  
  253.         valueLen = ord(st[pos])
  254.         pos = pos + 1
  255.         if valueLen & 0x80:  # same thing here...
  256.             b = map(ord, st[pos:pos+3])
  257.             pos = pos + 3
  258.             valueLen = ((valueLen & 0x7F) << 24) + (b[0] << 16) + (b[1] << 8) + b[2]
  259.  
  260.         # pull out the name and value and return with the updated position
  261.         return ( st[pos : pos+nameLen],
  262.                  st[pos + nameLen : pos + nameLen + valueLen],
  263.                  pos + nameLen + valueLen )
  264.  
  265.  
  266.     def writePair(name, value):
  267.         """
  268.         Opposite of readPair
  269.         """
  270.         l = len(name)
  271.         if l < 0x80:
  272.             st = chr(l)
  273.         else:
  274.             st = chr(0x80 | (l >> 24) & 0xFF) + chr((l >> 16) & 0xFF) + \
  275.                  chr((l >> 8) & 0xFF) + chr(l & 0xFF)
  276.  
  277.         l = len(value)
  278.         if l < 0x80:
  279.             st = st + chr(l)
  280.         else:
  281.             st = st + chr(0x80 | (l >> 24) & 0xFF) + chr((l >> 16) & 0xFF) + \
  282.                  chr((l >> 8) & 0xFF) + chr(l & 0xFF)
  283.  
  284.         return st + name + value
  285.  
  286.  
  287.  
  288.     def getRecordAsString(self):
  289.         """
  290.         Format the record to be sent back to the web server.
  291.         """
  292.         content = self.content
  293.         if self.recType == FCGI_BEGIN_REQUEST:
  294.             content = chr(self.role>>8) + chr(self.role & 0xFF) + \
  295.                       chr(self.flags) + 5*'\000'
  296.  
  297.         elif self.recType == FCGI_UNKNOWN_TYPE:
  298.             content = chr(self.unknownType) + 7*'\000'
  299.  
  300.         elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
  301.             content = ""
  302.             for i in self.values.keys():
  303.                 content = content + self.writePair(i, self.values[i])
  304.  
  305.         elif self.recType == FCGI_END_REQUEST:
  306.             v = self.appStatus
  307.             content = chr((v >> 24) & 0xFF) + chr((v >> 16) & 0xFF) + \
  308.                       chr((v >> 8) & 0xFF) + chr(v & 0xFF)
  309.             content = content + chr(self.protocolStatus) + 3*'\000'
  310.  
  311.         cLen = len(content)
  312.         eLen = (cLen + 7) & (0xFFFF - 7)    # align to an 8-byte boundary
  313.         padLen = eLen - cLen
  314.  
  315.         hdr = [ self.version,
  316.                 self.recType,
  317.                 self.reqId >> 8,
  318.                 self.reqId & 0xFF,
  319.                 cLen >> 8,
  320.                 cLen & 0xFF,
  321.                 padLen,
  322.                 0]
  323.         hdr = string.join(map(chr, hdr), '')
  324.         return hdr + content + padLen * '\000'
  325.  
  326.  
  327. #----------------------------------------------------------------------
  328.  
  329. class FCGIChannel(asynchat.async_chat):
  330.     """
  331.     Process a FastCGI connection.  This class implements most of the
  332.     Application Server side of the protocol defined in
  333.     http://www.fastcgi.com/fcgi-devkit-2.1/doc/fcgi-spec.html (which is
  334.     the FastCGI Specification 1.0 from Open Market, Inc.) in a manner
  335.     that is compatible with the asyncore medusa engine of ZServer.
  336.  
  337.     The main ommission from the spec is support for multiplexing
  338.     multiple requests on a single connection, but since none of the
  339.     web servers support it (that I know of,) and since ZServer can
  340.     easily multiplex multiple connections in the same process, it's no
  341.     great loss.
  342.     """
  343.     closed=0
  344.     using_temp_stdin=None
  345.     
  346.     def __init__(self, server, sock, addr):
  347.         self.server = server
  348.         self.addr = addr
  349.         asynchat.async_chat.__init__(self, sock)
  350.         self.setInitialState()
  351.         self.remainingRecs = 1  # We have to read at least one
  352.         self.env = {}
  353.         self.stdin = StringIO()
  354.         self.filterData = StringIO()  # not currently used, but maybe someday
  355.         self.requestId = 0
  356.  
  357.  
  358.     def setInitialState(self):
  359.         self.data = StringIO()
  360.         self.curRec = None
  361.         self.set_terminator(8) # FastCGI record header size.
  362.  
  363.  
  364.     def readable(self):
  365.         return self.remainingRecs != 0
  366.  
  367.  
  368.     def collect_incoming_data(self, data):
  369.         self.data.write(data)
  370.  
  371.  
  372.     def found_terminator(self):
  373.         # Are we starting a new record?  If so, data is the header.
  374.         if not self.curRec:
  375.             self.curRec = FCGIRecord(self.data.getvalue())
  376.             if self.curRec.needMore():
  377.                 self.set_terminator(self.curRec.needMore())
  378.                 self.data = StringIO()
  379.                 return
  380.  
  381.         rec = self.curRec
  382.  
  383.         # If waiting for record content, give it to the record.
  384.         if rec.needContent():
  385.             rec.parseContent(self.data.getvalue())
  386.             if rec.needMore():
  387.                 self.set_terminator(rec.needMore())
  388.                 self.data = StringIO()
  389.                 return
  390.  
  391.         if rec.needPadding():
  392.             rec.gotPadding()
  393.  
  394.  
  395.         # If we get this far without returning, we've got the whole
  396.         # record.  Figure out what to do with it.
  397.  
  398.         if rec.recType in FCGI_ManagementTypes:
  399.             # Apache mod_fastcgi doesn't send these, but others may
  400.             self.handleManagementTypes(rec)
  401.  
  402.         elif rec.reqId == 0:
  403.             # It's a management record of unknown type.
  404.             # Complain about it...
  405.             r2 = FCGIRecord()
  406.             r2.recType = FCGI_UNKNOWN_TYPE
  407.             r2.unknownType = rec.recType
  408.             self.push(r2.getRecordAsString(), 0)
  409.  
  410.  
  411.         # Since we don't actually have to do anything to ignore the
  412.         # following conditions, they have been commented out and have
  413.         # been left in the code for documentation purposes.
  414.  
  415.         # Ignore requests that aren't active
  416.         # elif rec.reqId != self.requestId and rec.recType != FCGI_BEGIN_REQUEST:
  417.         #     pass
  418.         #
  419.         # If we're already doing a request, ignore further BEGIN_REQUESTs
  420.         # elif rec.recType == FCGI_BEGIN_REQUEST and self.requestId != 0:
  421.         #     pass
  422.  
  423.  
  424.         # Begin a new request
  425.         elif rec.recType == FCGI_BEGIN_REQUEST and self.requestId == 0:
  426.             self.requestId = rec.reqId
  427.             if rec.role == FCGI_AUTHORIZER:   self.remainingRecs = 1
  428.             elif rec.role == FCGI_RESPONDER:  self.remainingRecs = 2
  429.             elif rec.role == FCGI_FILTER:     self.remainingRecs = 3
  430.  
  431.         # Read some name-value pairs (the CGI environment)
  432.         elif rec.recType == FCGI_PARAMS:
  433.             if rec.contentLength == 0:  # end of the stream
  434.             
  435.                 if self.env.has_key('REQUEST_METHOD'):
  436.                     method=self.env['REQUEST_METHOD']
  437.                 else:
  438.                     method='GET'
  439.                 if self.env.has_key('PATH_INFO'):
  440.                     path=self.env['PATH_INFO']
  441.                 else:
  442.                     path=''
  443.                 DebugLogger.log('B', id(self), '%s %s' % (method, path))       
  444.      
  445.                 self.remainingRecs = self.remainingRecs - 1
  446.                 self.content_length=string.atoi(self.env.get(
  447.                     'CONTENT_LENGTH','0'))
  448.             else:
  449.                 self.env.update(rec.values)
  450.  
  451.         # read some stdin data
  452.         elif rec.recType == FCGI_STDIN:
  453.             if rec.contentLength == 0:  # end of the stream
  454.                 self.remainingRecs = self.remainingRecs - 1
  455.             else:
  456.                 # see if stdin is getting too big, and 
  457.                 # replace it with a tempfile if necessary
  458.                 if len(rec.content) + self.stdin.tell() > 1048576 and \
  459.                         not self.using_temp_stdin:
  460.                     t=TemporaryFile()
  461.                     t.write(self.stdin.getvalue())
  462.                     self.stdin=t
  463.                     self.using_temp_stdin=1
  464.                 self.stdin.write(rec.content)
  465.  
  466.  
  467.         # read some filter data
  468.         elif rec.recType == FCGI_DATA:
  469.             if rec.contentLength == 0:  # end of the stream
  470.                 self.remainingRecs = self.remainingRecs - 1
  471.             else:
  472.                 self.filterData.write(rec.content)
  473.  
  474.  
  475.         # We've processed the record.  Now what do we do?
  476.         if self.remainingRecs > 0:
  477.             # prepare to get the next record
  478.             self.setInitialState()
  479.  
  480.         else:
  481.             # We've got them all.  Let ZPublisher do its thang.
  482.  
  483.             DebugLogger.log('I', id(self), self.stdin.tell())
  484.  
  485.             # But first, fixup the auth header if using newest mod_fastcgi.
  486.             if self.env.has_key('Authorization'):
  487.                 self.env['HTTP_AUTHORIZATION'] = self.env['Authorization']
  488.  
  489.             self.stdin.seek(0)
  490.             self.send_response()
  491.  
  492.  
  493.  
  494.     def send_response(self):
  495.         """
  496.         Create output pipes, request, and response objects.  Give them
  497.         to ZPublisher for processing.
  498.         """
  499.         response = FCGIResponse(stdout = FCGIPipe(self, FCGI_STDOUT),
  500.                                 stderr = StringIO())
  501.         response.setChannel(self)
  502.         request  = HTTPRequest(self.stdin, self.env, response)
  503.         handle(self.server.module, request, response)
  504.  
  505.  
  506.     def log_request(self, bytes):
  507.         
  508.         DebugLogger.log('E', id(self))
  509.         
  510.         if self.env.has_key('PATH_INFO'):
  511.             path=self.env['PATH_INFO']
  512.         else:
  513.             path=''
  514.         if self.env.has_key('REQUEST_METHOD'):
  515.             method=self.env['REQUEST_METHOD']
  516.         else:
  517.             method="GET"
  518.         if self.addr:
  519.             self.server.logger.log (
  520.                 self.addr[0],
  521.                 '%s - - [%s] "%s %s" %d %d' % (
  522.                     self.addr[1],
  523.                     time.strftime (
  524.                     '%d/%b/%Y:%H:%M:%S ',
  525.                     time.gmtime(time.time())
  526.                     ) + tz_for_log,
  527.                     method, path, self.reply_code, bytes
  528.                     )
  529.                 )
  530.         else:
  531.             self.server.logger.log (
  532.                 '127.0.0.1',
  533.                 '- - [%s] "%s %s" %d %d' % (
  534.                     time.strftime (
  535.                     '%d/%b/%Y:%H:%M:%S ',
  536.                     time.gmtime(time.time())
  537.                     ) + tz_for_log,
  538.                     method, path, self.reply_code, bytes
  539.                     )
  540.                 )
  541.  
  542.  
  543.  
  544.     def handleManagementTypes(self, rec):
  545.         """
  546.         The web server has asked us what features we support...
  547.         """
  548.         if rec.recType == FCGI_GET_VALUES:
  549.             rec.recType = FCGI_GET_VALUES_RESULT
  550.             vars={'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
  551.                   'FCGI_MAX_REQS'  : FCGI_MAX_REQS,
  552.                   'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
  553.             rec.values = vars
  554.             self.push(rec.getRecordAsString(), 0)
  555.  
  556.  
  557.     def sendDataRecord(self, data, recType):
  558.         rec = FCGIRecord()
  559.         rec.recType = recType
  560.         rec.reqId   = self.requestId
  561.         # Can't send more than 64K minus header size.  8K seems about right.
  562.         if type(data)==type(''):
  563.             # send some string data
  564.             while data:
  565.                 chunk = data[:8192]
  566.                 data = data[8192:]
  567.                 rec.content = chunk
  568.                 self.push(rec.getRecordAsString(), 0)
  569.         else:
  570.             # send a producer
  571.             p, cLen=data
  572.             eLen = (cLen + 7) & (0xFFFF - 7)    # align to an 8-byte boundary
  573.             padLen = eLen - cLen
  574.  
  575.             hdr = [ rec.version,
  576.                     rec.recType,
  577.                     rec.reqId >> 8,
  578.                     rec.reqId & 0xFF,
  579.                     cLen >> 8,
  580.                     cLen & 0xFF,
  581.                     padLen,
  582.                     0]
  583.             hdr = string.join(map(chr, hdr), '')
  584.             self.push(hdr, 0)
  585.             self.push(p, 0)
  586.             self.push(padLen * '\000', 0)            
  587.  
  588.     def sendStreamTerminator(self, recType):
  589.         rec = FCGIRecord()
  590.         rec.recType = recType
  591.         rec.reqId   = self.requestId
  592.         rec.content = ""
  593.         self.push(rec.getRecordAsString(), 0)
  594.  
  595.     def sendEndRecord(self, appStatus=0):
  596.         rec = FCGIRecord()
  597.         rec.recType        = FCGI_END_REQUEST
  598.         rec.reqId          = self.requestId
  599.         rec.protocolStatus = FCGI_REQUEST_COMPLETE
  600.         rec.appStatus      = appStatus
  601.         self.push(rec.getRecordAsString(), 0)
  602.         self.requestId     = 0
  603.  
  604.     def push(self, producer, send=1):
  605.         # this is thread-safe when send is false
  606.         # note, that strings are not wrapped in 
  607.         # producers by default
  608.         if self.closed:
  609.             return
  610.         self.producer_fifo.push(producer)
  611.         if send: self.initiate_send()
  612.  
  613.     push_with_producer=push
  614.  
  615.     def close(self):
  616.         self.closed=1
  617.         while self.producer_fifo:
  618.             p=self.producer_fifo.first()
  619.             if p is not None and type(p) != StringType:
  620.                 p.more() # free up resources held by producer
  621.             self.producer_fifo.pop()
  622.         asyncore.dispatcher.close(self)
  623.  
  624. #----------------------------------------------------------------------
  625.  
  626. class FCGIServer(asyncore.dispatcher):
  627.     """
  628.     Listens for and accepts FastCGI requests and hands them off to a
  629.     FCGIChannel for handling.
  630.  
  631.     FCGIServer can be configured to listen on either a specific port
  632.     (for inet sockets) or socket_file (for unix domain sockets.)
  633.  
  634.     For inet sockets, the ip argument specifies the address from which
  635.     the server will accept connections, '' indicates all addresses. If
  636.     you only want to accept connections from the localhost, set ip to
  637.     '127.0.0.1'.
  638.     """
  639.  
  640.     channel_class=FCGIChannel
  641.  
  642.     def __init__(self,
  643.                  module='Main',
  644.                  ip='127.0.0.1',
  645.                  port=None,
  646.                  socket_file=None,
  647.                  resolver=None,
  648.                  logger_object=None):
  649.  
  650.         self.ip = ip
  651.         self.count=counter()
  652.         asyncore.dispatcher.__init__(self)
  653.         if not logger_object:
  654.             logger_object = logger.file_logger(sys.stdout)
  655.         if resolver:
  656.             self.logger = logger.resolving_logger(resolver, logger_object)
  657.         else:
  658.             self.logger = logger.unresolving_logger(logger_object)
  659.  
  660.         # get configuration
  661.         self.module = module
  662.         self.port = port
  663.         self.socket_file = socket_file
  664.  
  665.         # setup sockets
  666.         if self.port:
  667.             self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  668.             self.set_reuse_addr()
  669.             self.bind((self.ip, self.port))
  670.         else:
  671.             try:
  672.                 os.unlink(self.socket_file)
  673.             except os.error:
  674.                 pass
  675.             self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
  676.             self.set_reuse_addr()
  677.             self.bind(self.socket_file)
  678.             try:
  679.                 os.chmod(self.socket_file,0777)
  680.             except os.error:
  681.                 pass
  682.         self.listen(256)
  683.         self.log_info('FastCGI Server (V%s) started at %s\n'
  684.                       '\tIP          : %s\n'
  685.                       '\tPort        : %s\n'
  686.                       '\tSocket path : %s\n'
  687.                       % (__version__, time.ctime(time.time()), self.ip,
  688.                          self.port, self.socket_file))
  689.  
  690.  
  691.  
  692.     def handle_accept(self):
  693.         self.count.increment()
  694.         try:
  695.             conn, addr = self.accept()
  696.         except socket.error:
  697.             self.log_info('Server accept() threw an exception', 'warning')
  698.             return
  699.         self.channel_class(self, conn, addr)
  700.  
  701.  
  702.     def readable(self):
  703.         return len(asyncore.socket_map) < CONNECTION_LIMIT
  704.  
  705.  
  706.     def writable (self):
  707.         return 0
  708.  
  709.  
  710.     def listen(self, num):
  711.         # override asyncore limits for nt's listen queue size
  712.         self.accepting = 1
  713.         return self.socket.listen(num)
  714.  
  715. #----------------------------------------------------------------------
  716.  
  717. class FCGIResponse(HTTPResponse):
  718.   
  719.     _tempfile=None
  720.     _templock=None
  721.     _tempstart=0
  722.     
  723.     def setChannel(self, channel):
  724.         self.channel = channel
  725.  
  726.     def write(self, data):
  727.         stdout=self.stdout
  728.         
  729.         if not self._wrote:
  730.             l=self.headers.get('content-length', None)
  731.             if l is not None:
  732.                 try:
  733.                     if type(l) is type(''): l=string.atoi(l)
  734.                     if l > 128000:
  735.                         self._tempfile=TemporaryFile()
  736.                         self._templock=thread.allocate_lock()
  737.                 except: pass
  738.                     
  739.             stdout.write(str(self))
  740.             self._wrote=1
  741.  
  742.         if not data: return
  743.  
  744.         t=self._tempfile
  745.         if t is None:
  746.             stdout.write(data)
  747.         else:
  748.             while data:
  749.                 # write file producers
  750.                 # each producer holds 32K data
  751.                 chunk=data[:32768]
  752.                 data=data[32768:]
  753.                 l=len(chunk)
  754.                 b=self._tempstart
  755.                 e=b+l
  756.                 self._templock.acquire()
  757.                 try:
  758.                     t.seek(b)
  759.                     t.write(chunk)
  760.                 finally:
  761.                     self._templock.release()
  762.                 self._tempstart=e
  763.                 stdout.write((file_part_producer(t,self._templock,b,e), l))
  764.  
  765.     def _finish(self):
  766.         self.channel.reply_code=self.status
  767.     
  768.         DebugLogger.log('A', id(self.channel), '%d %d' % (
  769.                 self.status, self.stdout.length))
  770.     
  771.         t=self._tempfile
  772.         if t is not None:
  773.             self.stdout.write((file_close_producer(t), 0))
  774.         self._tempfile=None
  775.  
  776.         self.channel.sendStreamTerminator(FCGI_STDOUT)
  777.         self.channel.sendEndRecord()
  778.         self.stdout.close()
  779.         self.stderr.close()
  780.  
  781.         # The following was adapted from PCGIPipe.finish and PCGIPipe.close
  782.         # I don't really understand it enough to know if I got it right...
  783.         shutdown = 0
  784.         if self.headers.get('bobo-exception-type','') == \
  785.                 'exceptions.SystemExit':
  786.             r = self.headers.get('bobo-exception-value','0')
  787.             try: r=string.atoi(r)
  788.             except: r = r and 1 or 0
  789.             shutdown = r,
  790.  
  791.         if not self.channel.closed:
  792.             self.channel.push_with_producer(LoggingProducer(self.channel,
  793.                                                             self.stdout.length,
  794.                                                             'log_request'), 0)
  795.         if shutdown:
  796.             sys.ZServerExitCode = shutdown[0]
  797.             self.channel.push(ShutdownProducer(), 0)
  798.             Wakeup(lambda: asyncore.close_all())
  799.         else:
  800.             self.channel.push(None,0)
  801.             Wakeup()
  802.         self.channel=None
  803.  
  804.  
  805.  
  806. #----------------------------------------------------------------------
  807.  
  808. class FCGIPipe:
  809.     """
  810.     This class acts like a file and is used to catch stdout/stderr
  811.     from ZPublisher and create FCGI records out of the data stream to
  812.     send back to the web server.
  813.     """
  814.     def __init__(self, channel, recType):
  815.         self.channel = channel
  816.         self.recType = recType
  817.         self.length  = 0
  818.  
  819.     def write(self, data):
  820.         if type(data)==type(''):
  821.             datalen = len(data)
  822.         else:
  823.             p, datalen = data
  824.         if data:
  825.             self.channel.sendDataRecord(data, self.recType)
  826.             self.length = self.length + datalen
  827.  
  828.     def close(self):
  829.         self.channel = None
  830.  
  831.  
  832. #----------------------------------------------------------------------
  833.  
  834.  
  835.  
  836.